Java 26-Day Course - Day 13: Abstract Classes and Interfaces

Day 13: Abstract Classes and Interfaces

Abstract classes and interfaces are core tools for object-oriented design. An abstract class is a “partially completed blueprint,” while an interface is a “contract (specification).” Neither can be instantiated directly — child classes must provide concrete implementations.

Abstract Classes

Declared with the abstract keyword, they can contain both abstract methods (methods without a body) and regular methods.

abstract class GameCharacter {
    String name;
    int hp;
    int attackPower;

    GameCharacter(String name, int hp, int attackPower) {
        this.name = name;
        this.hp = hp;
        this.attackPower = attackPower;
    }

    // Abstract method: must be implemented by child classes
    abstract void attack(GameCharacter target);
    abstract void specialSkill();

    // Regular method: common logic
    void takeDamage(int damage) {
        hp -= damage;
        System.out.println(name + " took " + damage + " damage! (HP: " + hp + ")");
        if (hp <= 0) {
            System.out.println(name + " has fallen!");
        }
    }

    void showStatus() {
        System.out.println("[" + name + "] HP: " + hp + " / Attack: " + attackPower);
    }
}

class Warrior extends GameCharacter {
    Warrior(String name) {
        super(name, 200, 30);
    }

    @Override
    void attack(GameCharacter target) {
        System.out.println(name + " attacks " + target.name + " with a sword!");
        target.takeDamage(attackPower);
    }

    @Override
    void specialSkill() {
        System.out.println(name + "'s Fury Strike! (Attack power doubled)");
        attackPower *= 2;
    }
}

class Mage extends GameCharacter {
    int mana;

    Mage(String name) {
        super(name, 100, 50);
        this.mana = 150;
    }

    @Override
    void attack(GameCharacter target) {
        if (mana >= 20) {
            System.out.println(name + " attacks " + target.name + " with a fireball!");
            target.takeDamage(attackPower);
            mana -= 20;
        } else {
            System.out.println("Not enough mana!");
        }
    }

    @Override
    void specialSkill() {
        System.out.println(name + "'s Meteor Strike!");
        mana -= 80;
    }
}

public class AbstractClassExample {
    public static void main(String[] args) {
        // GameCharacter gc = new GameCharacter(...); // Error! Cannot instantiate abstract class
        Warrior warrior = new Warrior("Warrior Arthur");
        Mage mage = new Mage("Mage Merlin");

        warrior.showStatus();
        mage.showStatus();

        warrior.attack(mage);
        mage.specialSkill();
        mage.attack(warrior);
    }
}

Interfaces

Interfaces define the method specifications that a class must implement. Use implements to implement them, and multiple implementations are allowed.

interface Flyable {
    void fly();
    int getMaxAltitude();
}

interface Swimmable {
    void swim();
    int getMaxDepth();
}

interface Runnable {
    void run();
    int getMaxSpeed();
}

// Multiple interface implementation
class Duck implements Flyable, Swimmable, Runnable {
    String name;

    Duck(String name) {
        this.name = name;
    }

    @Override
    public void fly() {
        System.out.println(name + " flies into the sky!");
    }

    @Override
    public int getMaxAltitude() {
        return 500;
    }

    @Override
    public void swim() {
        System.out.println(name + " swims on the water!");
    }

    @Override
    public int getMaxDepth() {
        return 2;
    }

    @Override
    public void run() {
        System.out.println(name + " waddles along!");
    }

    @Override
    public int getMaxSpeed() {
        return 10;
    }
}

class Penguin implements Swimmable, Runnable {
    String name;

    Penguin(String name) {
        this.name = name;
    }

    @Override
    public void swim() {
        System.out.println(name + " swims rapidly!");
    }

    @Override
    public int getMaxDepth() {
        return 100;
    }

    @Override
    public void run() {
        System.out.println(name + " waddles quickly!");
    }

    @Override
    public int getMaxSpeed() {
        return 5;
    }
}

public class InterfaceExample {
    // Declare parameters as interface types (polymorphism)
    static void letItFly(Flyable f) {
        f.fly();
        System.out.println("Max altitude: " + f.getMaxAltitude() + "m");
    }

    static void letItSwim(Swimmable s) {
        s.swim();
        System.out.println("Max depth: " + s.getMaxDepth() + "m");
    }

    public static void main(String[] args) {
        Duck duck = new Duck("Donald");
        Penguin penguin = new Penguin("Pororo");

        letItFly(duck);
        letItSwim(duck);
        letItSwim(penguin);
        // letItFly(penguin); // Compile error! Penguin is not Flyable
    }
}

Default Methods and Static Methods

Since Java 8, interfaces can also define methods with implementations.

interface Logger {
    // Abstract method
    void log(String message);

    // Default method: provides a default implementation
    default void info(String message) {
        log("[INFO] " + message);
    }

    default void error(String message) {
        log("[ERROR] " + message);
    }

    default void warn(String message) {
        log("[WARN] " + message);
    }

    // Static method: called using the interface name
    static Logger consoleLogger() {
        return message -> System.out.println(message);
    }
}

class FileLogger implements Logger {
    @Override
    public void log(String message) {
        // Instead of writing to a file, we display on console here
        System.out.println("[FILE] " + message);
    }

    // Default methods can be overridden if needed
    @Override
    public void error(String message) {
        log("[CRITICAL ERROR] " + message);
    }
}

public class DefaultMethodExample {
    public static void main(String[] args) {
        Logger console = Logger.consoleLogger();
        console.info("Server started");
        console.error("Connection failed");
        console.warn("Low memory");

        Logger fileLogger = new FileLogger();
        fileLogger.info("Request processed");
        fileLogger.error("Disk space low"); // Overridden version
    }
}

Abstract Class vs Interface Comparison

// Abstract class: "is-a" relationship
// - Can have state (fields)
// - Can have constructors
// - Only single inheritance
// - Use when you need common logic + enforced abstract methods

abstract class Vehicle {
    int speed;
    abstract void move();
    void stop() { speed = 0; }
}

// Interface: "can-do" relationship
// - Cannot have state (only constants)
// - No constructors
// - Multiple implementation allowed
// - Use when defining capabilities (roles)

interface Chargeable {
    void charge();
    int getBatteryLevel();
}

interface GPSEnabled {
    String getCurrentLocation();
}

// Combination: abstract class inheritance + multiple interface implementation
class ElectricScooter extends Vehicle implements Chargeable, GPSEnabled {
    int battery = 100;

    @Override
    void move() { System.out.println("The electric scooter rides!"); }

    @Override
    public void charge() { battery = 100; }

    @Override
    public int getBatteryLevel() { return battery; }

    @Override
    public String getCurrentLocation() { return "Seoul, Gangnam-gu"; }
}

Today’s Exercises

  1. Payment System Design: Define a Payable interface (methods: pay(long amount), refund(long amount)) and implement it with CreditCard, BankAccount, and CryptoCurrency classes.

  2. Sortable Collection: Create a Student class that implements the Comparable<T> interface. It has name and score fields, and sorts in descending order by score. Test with Arrays.sort().

  3. Game Character System: Combine an abstract class Character (name, HP) with interfaces Healable and Buffable. Paladin implements both interfaces, while Rogue implements only Buffable.

Was this article helpful?